package ca.etsmtl.log120.tp1.lists;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Observable;

import ca.etsmtl.log120.tp1.comparators.ShapeComparatorByArea;
import ca.etsmtl.log120.tp1.comparators.ShapeComparatorByDist;
import ca.etsmtl.log120.tp1.comparators.ShapeComparatorByHeight;
import ca.etsmtl.log120.tp1.comparators.ShapeComparatorBySeq;
import ca.etsmtl.log120.tp1.comparators.ShapeComparatorByShape;
import ca.etsmtl.log120.tp1.comparators.ShapeComparatorByWidth;
import ca.etsmtl.log120.tp1.shapehierarchy.AbstractShape;

/******************************************************
Cours : LOG120
Session : E2011
Groupe : 01
Projet : Laboratoire #2
Etudiants : Nicolas Blais Miko
 	 		Yohan Trepanier-Montpetit
Codes perm. : BLAN24079109
			  TREY
Professeur : Chamseddine Talhi
Charge de labo : Eric Goulet
Nom du fichier : Oval.java
Date cree : 2011-05-18
Date dern. modif. 2015-05-23
 *************************************************** ****
Historique des modifications
 *************************************************** ****
2011-05-18 Version initiale (Nicolas Blais Miko)
2011-05-24 Implementation de drawShape() (Yohan Trepanier)
 ***************************************************/

/**
 * Classe pouvant garder plusieurs formes dans une liste chainee et les trier selon differents parametres
 */
public class ShapeLinkedList extends Observable implements List<AbstractShape>{

	// La longueur de la liste
	private int tailleListe;

	Node courant = null;
	Node debut = null;
	Node fin = null;

	/**
	 * Classe noeud de la liste chainee
	 * @param forme La forme a garder dans le noeud
	 * @param next Lien vers le prochain noeud
	 */
	public class Node{

		AbstractShape forme;
		Node next;

		/**
		 * Constructeur par defaut
		 */
		public Node(){}

		/**
		 * Createur parametrique
		 * @param forme La forme pour ce noeud
		 * @param next Le lien vers le prochain noeud
		 */
		public Node(AbstractShape forme, Node next){

			this.forme = forme;
			this.next = next;
		}
	}

	/**
	 * Creation d'une liste d'une certaine taille
	 * @param taille La taille de la liste
	 */
	public ShapeLinkedList(int taille){

		tailleListe = taille;
	}

	/**
	 * Methode qui ajoute une forme a la liste 
	 * Lance une exception "Liste pleine" si on a depasse la limite de la liste
	 */
	@Override
	public boolean add(AbstractShape shape) {

		courant = debut;

		//Si c'est le premier element a etre ajoute a la liste
		if(debut == null){
			courant = new Node(shape, null); 
			debut = courant;
			fin = courant;

		}else{ //Si on a deja un element (ou plus)

			int count = 0; //On cree une variable pour parcourir la liste

			//Tant qu'il y a un autre element, on incremente et on passe au prochain
			//on se rend a la fin
			while(courant.next != null){ 

				courant = courant.next;
				count++;
			}

			if(count < tailleListe - 1){ //Si on a pas atteint la fin de le liste

				courant.next = new Node(shape, null);
				fin = courant.next;
			} else{
				try {
					throw new Exception("Liste pleine");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

		}

		//On avise les amis!!!
		setChanged();
		notifyObservers();
		return true;
	}

	/**
	 * Methode d'ajout permettant de remplacer la forme a un certain indice dans la liste
	 */
	@Override
	public void add(int indice, AbstractShape shape) {
		courant = debut; //On met l'indice courant au debut de la liste

		int count = 0; //Variable pour parcourir la liste

		while(count < indice){ //On parcours la liste jusqu'a l'indice recu

			if(courant != null && courant.next != null){ //On avance tant que le noeud actuel et le prochain ne sont pas null

				courant = courant.next;
				count++;

				courant.forme = shape; 
			}	
		}
	}

	/**
	 * Methode qui remplace la forme a l'indice specifie par la forme recu en parametre
	 * @param indice l'indice ou remplacer la forme
	 * @param shape La forme avec laquelle on veut remplacer 
	 */
	public void replace(int indice, AbstractShape shape){
		int count = 0;
		courant = debut;
		while(count < indice){ //On parcours la liste jusqu'a l'indice recu
			count++;
			courant = courant.next;
		}
		courant.forme = shape;
	}

	@Override
	public boolean addAll(Collection<? extends AbstractShape> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean addAll(int arg0, Collection<? extends AbstractShape> arg1) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Methode permettant de vider la liste
	 */
	public void clear() {

		courant = debut = fin = null;

		setChanged();
		notifyObservers();
	}

	@Override
	public boolean contains(Object arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean containsAll(Collection<?> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Methode permettant d'aller chercher la forme a l'indice recu en parametre
	 * @param
	 */
	public AbstractShape get(int arg0) {
		courant = debut;
		int count = 0;
		while(count < arg0){ //On parcours la liste jusqu'a l'indice recu
			count++;
			courant = courant.next;

		}
		return courant.forme;
	}

	/**
	 * Methode qui retourne l'index d'un objet recu en parametre
	 * @param arg0 l'objet dont on cherche l'index dans la liste
	 * @return Retourne l'index de l'objet ou -1 si l'objet est absent 
	 */
	public int indexOf(Object arg0) {

		courant = debut;

		int count = 0;

		while(courant != null && !courant.equals(arg0)){

			count++;
		}

		if(courant.equals(arg0)){

			return count;
		}else
			return -1;
	}

	/**
	 * Methode qui permet de savoir si la liste est vide
	 * @return True si la liste est vide et False sinon
	 */
	@Override
	public boolean isEmpty() {

		if(debut == null)
			return true;
		else
			return false;
	}

	/**
	 * Methode qui retourne un iterateur de la liste
	 */
	public Iterator<AbstractShape> iterator() {

		return new MonIterateur<AbstractShape>(this);
	}

	@Override
	public int lastIndexOf(Object arg0) {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * Methode qui retourne un iterateur de la liste
	 */
	public ListIterator<AbstractShape> listIterator() {
		// TODO Auto-generated method stub
		return new MonIterateur<AbstractShape>(this);
	}

	@Override
	public ListIterator<AbstractShape> listIterator(int arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean remove(Object arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public AbstractShape remove(int arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean removeAll(Collection<?> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean retainAll(Collection<?> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public AbstractShape set(int arg0, AbstractShape arg1) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * Methode qui permet de connaitre la longueur de la liste
	 */
	public int size() {

		int count = 0;
		courant = debut;

		while(courant != null){

			courant = courant.next;
			count++;
		}

		return count;
	}

	@Override
	public List<AbstractShape> subList(int arg0, int arg1) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * Methode qui retourne un tableau d'objet representant le contenu de la liste
	 *  P.S Ce tableau retourne des copies des formes de la liste
	 */
	@Override
	public Object[] toArray() {
		// TODO Auto-generated method stub
		courant = debut;
		AbstractShape[] tabForme = new AbstractShape[10];

		int count = 0;

		while(courant != null){

			tabForme[count] = courant.forme.copyOf();
			courant = courant.next;
			count++;
		}

		return tabForme;
	}

	@Override
	public <T> T[] toArray(T[] arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * Methode qui affiche chaque element de la liste dans la console (pour des tests)
	 */
	public void afficheListe(){

		courant = debut;

		//Tant qu'il y a un autre element, on incremente et on passe au prochain
		while(courant != null){ 

			System.out.println(courant.forme);
			courant = courant.next;

		}
	}

	/**
	 * 
	 * 
	 * METHODES DE TRIES
	 * 
	 * 
	 */

	/**
	 * Trie de la liste selon l'aire en ordre croissant
	 */
	public void sortAreaUp(){

		Collections.sort(this,new ShapeComparatorByArea());
	}

	/**
	 * Trie de la liste selon l'aire en ordre decroissant
	 */
	public void sortAreaDown(){

		Collections.sort(this,new ShapeComparatorByArea());
		invertList();
	}

	/**
	 * Trie de la liste selon le numero de sequence en ordre croissant
	 */
	public void sortSeqUp(){

		Collections.sort(this, new ShapeComparatorBySeq());
	}

	/**
	 * Trie de la liste selon le numero de sequence en ordre decroissant
	 */
	public void sortSeqDown(){

		Collections.sort(this, new ShapeComparatorBySeq());
		invertList();
	}

	/**
	 * Trie de la liste selon la forme en ordre croissant (carre, rectangle, cercle, oval, ligne)
	 */
	public void sortShapeUp(){

		Collections.sort(this, new ShapeComparatorByShape());
	}

	/**
	 * Trie de la liste selon la forme en ordre decroissant (ligne, oval, cercle, rectangle, carre
	 */
	public void sortShapeDown(){

		Collections.sort(this, new ShapeComparatorByShape());
		invertList();
	}

	/**
	 * Trie de la liste selon la distance en ordre croissant
	 */
	public void sortDistUp(){

		Collections.sort(this, new ShapeComparatorByDist());
	}

	/**
	 * Trie de la liste selon la distance en ordre decroissant
	 */
	public void sortDistDown(){

		Collections.sort(this, new ShapeComparatorByDist());
		invertList();
	}

	/**
	 * Trie de la liste selon la hauteur en ordre croissant
	 */
	public void sortHeightUp(){

		Collections.sort(this, new ShapeComparatorByHeight());
	}

	/**
	 * Trie de la liste selon la hauteur en ordre decroissant
	 */
	public void sortHeightDown(){

		Collections.sort(this, new ShapeComparatorByHeight());
		invertList();
	}

	/**
	 * Trie de la liste selon la largeur en ordre croissant
	 */
	public void sortWidthUp(){

		Collections.sort(this, new ShapeComparatorByWidth());
	}

	/**
	 * Trie de la liste selon la largeur en ordre decroissant
	 */
	public void sortWidthDown(){

		Collections.sort(this, new ShapeComparatorByWidth());
		invertList();
	}

	/**
	 * Retourne vrai si un prochain element existe apres l'indice specifie
	 * @param indice L'indice precedent l'element qu'on veut verifier
	 * @return Vrai si une forme existe apres l'indice specifie
	 */
	public boolean nextExists(int indice) {
		courant = debut;

		int count = 0;

		while(count <= indice){

			courant = courant.next;
			count++;
		}

		return(!(courant.next==null));

	}

	/**
	 * Methode qui inverse la liste
	 */
	public void invertList(){

		//Creation d'un tableau representant la liste
		Object[] arrayForme = toArray();

		//On garde en memoire le dernier indice de la liste
		int indiceAjout = tailleListe - 1;

		//On remplace chaque element de la liste par son element miroir
		for(int i = 0; i < tailleListe; i++){

			replace(indiceAjout, (AbstractShape)arrayForme[i]);
			indiceAjout--;
		}

	}

	/**
	 * Methode pour reprendre l'ordre dans lequel on a reçu les formes
	 * @param tab Le tableau des formes dans l'ordre ou elles ont ete recues
	 */
	public void orderReset(AbstractShape[] tab){

		//On remplace tous les elements de la liste par l'element au meme indice dans le tableau
		for(int i = 0; i < tailleListe; i++){

			replace(i, tab[i]);
		}

		setChanged();
		notifyObservers();
	}

	/**
	 * Methode qui place la position des formes dans l'ecran selon leur ordre dans la liste
	 */
	public void updatePositions(){

		//Pour chaque forme dans la liste, on determine la nouvelle position de la forme sur l'ecran
		for(int i = 0; i < tailleListe; i++){

			get(i).setPosition(i*40,i*40);
		}

		setChanged();
		notifyObservers();
	}
}
